关于AI(深度学习)相关项目 K8s 部署的一些思考

对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》

写在前面


  • 工作中遇到,简单整理
  • 第一次接触,一些粗浅的思考
  • 理解不足小伙伴帮忙指正

对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》


部署方面

pod 调度方面

对于 AI 相关项目,一般镜像都比较大,尤其涉及的库比较多,基本都是以 GB 为单位,而且部分项目可能需要专门的硬件支持,所以尽可能的通过 亲和性(affinity)或者拓扑分布约束(topologySpreadConstraints)来对调度区域进行限制,减少对整个集群节点存储的占用,方便维护,这里需要分情况考虑,

如果部署 Pod 相对较少,可以考虑通过 亲和性来处理,控制在指定标签的节点上,但是可能存在Pod不会均匀分布的情况,如果希望均匀分布,可以使用拓扑分布约束。

如果部署 Pod 相对较多,考虑通过 拓扑分布约束来限制,限制 Pod 只能调度到指定的拓扑域,但是尽量不要亲和性约束和拓扑分布约束同时使用,维护起来相对不方便。

Pod资源限制

在资源使用方面,如果当前项目和业务项目位于同一集群,那么需要对所在命名空间做资源配额,这里可以考虑使用 LimitRange 结合 Resource Quotas 使用.

对于 内存的限制,根据实际分配情况进行配置,内存属于 不可压缩资源,所以 Cgroup 可以严格控制,对应 CPU 来说,配置需要小于当前约定的配额,CPU 属于可压缩资源,Cgroup 不能像内存那种做成严格限制,超过去只能是少分配处理时间,所以可能会有弹性的变化,已经超出去了,才会限制,不像内存,直接就申请不了那么多起不来,超过去直接 kill 掉。

Pod 数量扩展方面

对于 CPU/GPU 计算密集型的 Pod 来讲,Pod 不是越多越好,纵向的资源扩展(Pod资源配额)要优于横向的Pod扩展(Pod 数量), 多个 Pod 分配很少的资源,处理速度要远远低于 Pod 分配资源较多,但 Pod 数较少。具体情况需要具体分析,实际上还要结合项目需要去考虑,训练和预测可能还存在差异

横向扩展,不能依赖 service 提供的负载均衡能力

测试中发现,使用无状态控制器来部署项目核心服务提供能力,当 Pod 数量相对较多时, service 往往不能均匀的负载(底层使用 iptable ),存在两级分化

默认情况下 ,SVC 使用轮询模式 RoundRobin 的负载分发策略,实现方式略有不同

当使用 iptables 模式,iptables 负载均衡使用随机模式,每个对应的 endpoint 会添加一个自定义链,从 SVC 的 服务发布方式(NodePort,ClusterIP,LoadBalancer ) 到 对应的 Pod,当 Pod 比较多,匹配的概率会变的很小,下面为当 Pod 数为 2 的时候,iptables 的匹配概率为 0.50000000000

1
-A KUBE-SVC-K6NOPV6C6M2DHO7B -m comment --comment "velero/minio:console -> 10.244.169.89:9090" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OLNIRCQFCXAN5USW

当使用 ipvs 模式的时候,即依赖 Linux 内核 lvs 特性,使用的负载策略为 rr(轮询调度)

1
2
3
TCP  192.168.26.200:80 rr
-> 192.168.26.153:80 Masq 2 0 0
-> 192.168.26.154:80 Masq 2 0 0

解决办法可以考虑使用流量治理工具,比如 istio或者对 负载进行划分,降低负载域的范围

架构方面

数据处理 拉(pull)比推(push)更合适

AI 相关项目一般涉及的数据量都比较大,尤其涉及到一些张量之类的大数组, 实际测试发现,通过一个调度中心把数据推给对应的能力提供 Pod 处理,在速度上要比 对应该的能力提供 Pod 直接从中间件拉取数据处理慢的多的多。

推的方式需要考虑负载问题,以及每个 Pod 实际的饱和状态,而且涉及到多次数据通信,但是拉的话就不需要考虑这么多。具体的处理可以使用K8sjob 控制器来实现,根据数据量,创建对应 job 任务,而且每次处理的 Job 也可以分批次控制。

异步不等于快,吞吐量不等于处理速度,网络IO密集型和 CUP 密集型是两种不同场景

当前的 python 框架大多支持异步处理,利用 Python 的协程,基于 asyncio 事件循环,可以有很高的并发量,吞吐量,尤其是 ASGI 标准的框架(fastapi,tomado)等,但是需要注意的是, asyncio 事件循环 的高吞吐 面向 网络/IO 密集型的非阻塞处理,不适用 CPU 密集型,对于 CPU 密集型,任然会阻塞。

即使通过队列等方式做了处理,解决的也只是吞吐量,和处理速度没有关系。往往看上去处理完了,会发现程序内部积累了大量的协程,吃进去了,但是消化不了。

利用中间件交换数据,比通过通信协议直接交换数据 更适合 K8s 环境

通过中间件交换数据,解耦了 服务之间的绑定,细化了流程,不需要考虑数据在传输的丢失,二是使用中间件,存储处理的中间数据,更方便测试,可以灵活处理各个阶段对数据的处理


© 2018-2023 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

发布于

2023-10-26

更新于

2024-01-14

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×